#include "BinanceTrade.h"

BinanceTrade* BinanceTrade::tradeHandler = nullptr;

BinanceTrade* BinanceTrade::getInstance(const std::string &api_key,const std::string &secret_key) {
    if (tradeHandler == nullptr) {
        tradeHandler = new BinanceTrade(api_key,secret_key);
    }
    return tradeHandler;
}

std::string BinanceTrade::getSignature(const std::string& data){
    unsigned char* result;
    static char res_hexstring[65];
    result = HMAC(EVP_sha256(), secretKey.c_str(), secretKey.length(), (unsigned char*)data.c_str(), data.length(), NULL, NULL);
    for (int i = 0; i < 32; i++) {
        sprintf(&(res_hexstring[i * 2]), "%02x", result[i]);
    }
    return std::string(res_hexstring, 64);
}

std::string BinanceTrade::UnixTimestampMills() {  
    auto now = std::chrono::system_clock::now();  
    auto time_since_epoch = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();  
    // auto time_micro = std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch()).count();
    // std::cout<<time_micro<<std::endl;
    return std::to_string(time_since_epoch);  
} 

void BinanceTrade::place_order(const Order &ord){
    try {
        nlohmann::json order;
        order["id"] = "e2a85d9f-07a5-4f94-8d5f-789dc3deb097";
        order["method"] = "order.place";
        order["params"] = {
            {"symbol", ord.symbol},
            {"side", ord.side},
            {"type", ord.type},
            //{"price", ord.price},
            {"quantity", ord.sQuantity},
            //{"timeInForce", ord.timeInForce},
            {"timestamp", UnixTimestampMills()},
            {"apiKey", apiKey}
        };
        std::string params;
        for (auto& el : order["params"].items()) {
            if (!params.empty()) {
                params += "&";
            }
            params += el.key() + "=" + el.value().get<std::string>();
        }
        order["params"]["signature"] = getSignature(params);
        std::string orderStr = order.dump();
        std::cout << "[send order]: " << ord.side << " " << ord.symbol << " " << ord.sQuantity << " " << order["params"]["timestamp"] << std::endl;
        m_trade_client.send(handle_, orderStr, websocketpp::frame::opcode::text);
    } catch (const std::exception &e) {
        std::cerr << "Exception in place_order: " << e.what() << std::endl;
    }
}

void BinanceTrade::connect() {
    while (true) {
        websocketpp::lib::error_code ec;
        client::connection_ptr con = m_trade_client.get_connection(tradeUrl, ec);
        if (ec) {
            std::cout << "Could not create trade connection because: " << ec.message() << std::endl;
            return;
        }
        handle_ = con->get_handle();
        m_trade_client.connect(con);
        std::thread trade_ws(&BinanceTrade::run_trade_client, this);
        trade_ws.join();
        m_trade_client.reset();
    }
}

void BinanceTrade::run_trade_client() {
    m_trade_client.run();
}

void BinanceTrade::on_trade_message(websocketpp::connection_hdl hdl, message_ptr msg) {
    nlohmann::json json_trade = nlohmann::json::parse(std::string(msg->get_payload()));
    // std::cout << json_trade << std::endl;
    if(json_trade.contains("status") && json_trade["status"] == 400){
        logi("[trade failed]: {}",std::string(json_trade["error"].dump()));
        std::cout << "[trade failed]" << json_trade["error"] << std::endl;
    }
    if(json_trade.contains("status") && json_trade["status"] == 200){
        logi("[trade successed]: {}",std::string(json_trade["result"].dump()));
        std::cout << "[trade successed]" << json_trade["result"] << std::endl;
    }
}

void BinanceTrade::on_trade_open(websocketpp::connection_hdl hdl) {
    std::cout << "Connected to Current Trade WebSocket" << std::endl;
}

void BinanceTrade::on_trade_close(websocketpp::connection_hdl hdl) {
    std::cout << "Disconnected from Current Trade WebSocket" << std::endl;
}

BinanceTrade::BinanceTrade(const std::string &api_key,const std::string &secret_key):apiKey(api_key),secretKey(secret_key) {
    m_trade_client.clear_access_channels(websocketpp::log::alevel::all);
    m_trade_client.clear_error_channels(websocketpp::log::elevel::all);
    m_trade_client.init_asio();
    m_trade_client.set_tls_init_handler([](websocketpp::connection_hdl) {
        return websocketpp::lib::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::tlsv12);
    });
    m_trade_client.set_message_handler(std::bind(&BinanceTrade::on_trade_message, this, std::placeholders::_1, std::placeholders::_2));
    m_trade_client.set_open_handler(std::bind(&BinanceTrade::on_trade_open, this, std::placeholders::_1));
    m_trade_client.set_close_handler(std::bind(&BinanceTrade::on_trade_close, this, std::placeholders::_1));
}
